package ai.people.platform.archives.service.impl;

import ai.people.core.nebula.aspect.NebulaAutoRelease;
import ai.people.core.nebula.entity.NebulaGraph;
import ai.people.core.nebula.factory.NebulaSessionFactoryBean;
import ai.people.core.nebula.util.NebulaQueryUtils;
import ai.people.core.security.entity.AuthenticationUser;
import ai.people.core.security.support.RequestHolderUtil;
import ai.people.netmon.framework.businessenum.GraphBizEnum;
import ai.people.netmon.framework.constant.GraphDirection;
import ai.people.netmon.framework.domain.archives.entity.*;
import ai.people.netmon.framework.domain.archives.request.GangGroupListRequest;
import ai.people.netmon.framework.domain.archives.request.GangGroupOrgSaveOrUpdateRequest;
import ai.people.netmon.framework.domain.archives.request.GangGroupSaveOrUpdateRequest;
import ai.people.netmon.framework.domain.archives.vo.*;
import ai.people.netmon.framework.domain.graph.enums.GraphEdgeEnum;
import ai.people.netmon.framework.domain.graph.enums.GraphTagEnum;
import ai.people.netmon.framework.domain.graph.tags.GangGroupMembersTag;
import ai.people.netmon.framework.exception.enums.CommonEnum;
import ai.people.netmon.framework.model.response.PageResponse;
import ai.people.netmon.framework.utils.AssertUtils;
import ai.people.netmon.utils.StringPool;
import ai.people.netmon.utils.idworker.IdWorker;
import ai.people.platform.archives.mapper.GangGroupInfoMapper;
import ai.people.platform.archives.service.*;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.hash.Hashing;
import com.vesoft.nebula.client.graph.exception.IOErrorException;
import com.vesoft.nebula.client.graph.net.SessionWrapper;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author zhenglin
 * @since 2022-06-21
 */
@Service
@NebulaAutoRelease
public class GangGroupInfoServiceImpl extends ServiceImpl<GangGroupInfoMapper, GangGroupInfo> implements GangGroupInfoService {

    @Autowired
    GangGroupRelationService gangGroupRelationService;
    @Autowired
    GangGroupInfoMapper gangGroupInfoMapper;
    @Autowired
    NebulaSessionFactoryBean nebulaSessionFactoryBean;
    @Autowired
    GraphAdminService graphAdminService;
    @Autowired
    ArchivesBaseInfoService archivesBaseInfoService;
    @Autowired
    GraphVidService graphVidService;

    /**
     * 团伙列表
     *
     * @param request 请求
     * @return {@link PageResponse}<{@link List}<{@link GangGroupListVO}>>
     */
    @Override
    public PageResponse<GangGroupListVO> listGangGroup(GangGroupListRequest request) {
        Page<GangGroupInfo> page = new Page<>(request.getOriginPageNo(), request.getPageSize());
        Page<GangGroupInfo> result = gangGroupInfoMapper.pageCustom(page, request);
        List<GangGroupInfo> records = result.getRecords();
        if (CollectionUtils.isEmpty(records)) {
            return PageResponse.builderEmpty();
        }
        List<GangGroupListVO> list = new ArrayList<>();
        for (GangGroupInfo record : records) {
            list.add(new GangGroupListVO(record));
        }
        return PageResponse.builder(list, result.getTotal(), result.getPages(), result.getCurrent());
    }

    /**
     * 保存团伙(基本详情+组织关系)
     *
     * @param group 集团
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveGangGroupGraph(GangGroupSaveOrUpdateRequest group) throws Exception {
        AuthenticationUser authenticationUser = RequestHolderUtil.loginUser();
        // 保存团伙基本信息
        GangGroupInfoVO baseInfo = group.getBaseInfo();
        GangGroupInfo gangGroupInfo = new GangGroupInfo(baseInfo);
        gangGroupInfo.setCreatorId(authenticationUser.getUserId());
        gangGroupInfo.setCreatorName(authenticationUser.getUsername());
        boolean save = save(gangGroupInfo);
        AssertUtils.isTrue(save, CommonEnum.FAIL);

        // 保存团伙组织关系
        Long id = gangGroupInfo.getId();
        GangGroupOrgSaveOrUpdateRequest orgInfo = group.getOrgInfo();

        Set<String> vidList = new HashSet<>();
        //生成团伙vid
        SessionWrapper sessionWrapper = nebulaSessionFactoryBean.getObject();
        String gangGroupVid = NebulaQueryUtils.buildVid(GraphTagEnum.GANG_GROUP.vidPrefix, id);
        saveTreeToGraph(orgInfo, sessionWrapper, gangGroupVid, vidList);
        //保存团伙根vid关系
        saveGangGroupRootVidRelations(gangGroupVid, vidList);
    }

    private void saveGangGroupRootVidRelations(String gangGroupVid, Set<String> vidList) {
        if (CollectionUtils.isNotEmpty(vidList)) {
            GraphVid graphVid = new GraphVid();
            graphVid.setRootVid(gangGroupVid);
            graphVid.setBizType(GraphBizEnum.GANG_GROUP.biz);
            graphVid.setVids(StringUtils.join(vidList, StringPool.COMMA));
            graphVidService.saveConflict(graphVid);
        }
    }

    /**
     * 团伙详情
     *
     * @param id id
     * @return {@link GangGroupDetailVO}
     */
    @Override
    public GangGroupDetailVO getGangGroupGraph(Long id) throws Exception {
        GangGroupDetailVO vo = new GangGroupDetailVO();

        GangGroupInfo gangGroupInfo = getById(id);
        AssertUtils.isNotNull(gangGroupInfo, CommonEnum.FAIL);

        vo.setId(id);
        vo.setBaseInfo(new GangGroupInfoVO(gangGroupInfo));

        String gangGroupVid = NebulaQueryUtils.buildVid(GraphTagEnum.GANG_GROUP.vidPrefix, id);

        Set<String> gangGroupVidSet = getGangGroupRootVidSet(gangGroupVid);
        Set<GangGroupMembersGraphBO> members = handlePath(gangGroupVid, gangGroupVidSet);

        GangGroupOrgDetailVO root = new GangGroupOrgDetailVO();
        if (CollectionUtils.isNotEmpty(members)) {
            Set<String> archivesIds = members.stream().map(GangGroupMembersGraphBO::getArchivesId).collect(toSet());
            List<GangGroupOrgPersonBO> bos = archivesBaseInfoService.listForGangGroup(archivesIds);

            Map<Long, GangGroupOrgPersonBO> idMapping = bos.stream().collect(toMap(GangGroupOrgPersonBO::getId, Function.identity()));

            Map<String, List<GangGroupMembersGraphBO>> groupBy = members.stream().collect(groupingBy(GangGroupMembersGraphBO::getParentId));

            GangGroupMembersGraphBO bo = groupBy.get(gangGroupVid).get(0);
            String vid = bo.getVid();
            root.setId(vid);
            root.setGroupIdentity(bo.getGroupIdentity());
            root.setName(gangGroupInfo.buildGroupNameList());
            root.setArchivesId(bo.getArchivesId());
            root.setDegreeOfDanger(bo.getDegreeOfDanger() == null ? null : Integer.parseInt(bo.getDegreeOfDanger()));
            root.setChildren(buildChildrenTree(idMapping, groupBy, vid, new ArrayList<>()));
        }
        vo.setOrgInfo(root);
        return vo;
    }

    /**
     * 获取根vid 关系的ID集合
     *
     * @param gangGroupVid 帮组视频
     * @return {@link Set}<{@link String}>
     */
    private Set<String> getGangGroupRootVidSet(String gangGroupVid) {
        GraphVid one = graphVidService.getOne(new LambdaQueryWrapper<GraphVid>().eq(GraphVid::getRootVid, gangGroupVid).eq(GraphVid::getBizType, GraphBizEnum.GANG_GROUP.biz));
        Set<String> gangGroupVidSet = new HashSet<>();
        if (one != null && StringUtils.isNotBlank(one.getVids())) {
            gangGroupVidSet.addAll(Arrays.asList(one.getVids().split(StringPool.COMMA)));
        }
        return gangGroupVidSet;
    }

    private Set<GangGroupMembersGraphBO> handlePath(String gangGroupVid, Set<String> gangGroupVidSet) throws Exception {
        NebulaGraph path = graphAdminService.path(gangGroupVid, GraphDirection.FROM, gangGroupVidSet);

        Set<GangGroupMembersGraphBO> members = new HashSet<>();
        Set<NebulaGraph.NebulaPath> paths = path.getPaths();
        for (NebulaGraph.NebulaPath nebulaPath : paths) {
            NebulaGraph.NebulaNode from = nebulaPath.getFrom();
            NebulaGraph.NebulaNode to = nebulaPath.getTo();
            GangGroupMembersGraphBO toMember = buildNode(to);
            String parentId = null;
            String fromVid = (String) from.getVid();
            if (fromVid.equals(gangGroupVid)) {
                parentId = gangGroupVid;
            } else {
                parentId = fromVid;
            }
            if (toMember != null) {
                toMember.setParentId(parentId);
                members.add(toMember);
            }
        }
        return members;
    }

    /**
     * 团伙成员列表
     *
     * @return {@link List}<{@link GangGroupOrgPersonListVO}>
     */
    @Override
    public List<GangGroupOrgPersonListVO> listGangGroupPersonGraph(Long id) throws Exception {
        String gangGroupVid = NebulaQueryUtils.buildVid(GraphTagEnum.GANG_GROUP.vidPrefix, id);
        Set<String> gangGroupVidSet = getGangGroupRootVidSet(gangGroupVid);
        Set<GangGroupMembersGraphBO> nodes = handlePath(gangGroupVid, gangGroupVidSet);
        if (CollectionUtils.isEmpty(nodes)) {
            return Collections.emptyList();
        }
        Set<String> collect = nodes.stream().map(GangGroupMembersGraphBO::getArchivesId).collect(Collectors.toSet());

        List<GangGroupOrgPersonListBO> records = gangGroupRelationService.listPersonFromGraph(collect, id);
        Map<Long, GangGroupOrgPersonListBO> map = records.stream().collect(toMap(GangGroupOrgPersonListBO::getId, Function.identity()));

        GangGroupInfo one = getOne(new LambdaQueryWrapper<GangGroupInfo>().select(GangGroupInfo::getCreatorName).eq(GangGroupInfo::getId, id));
        String creatorName = one.getCreatorName();

        List<GangGroupOrgPersonListVO> list = new ArrayList<>();
        for (GangGroupMembersGraphBO node : nodes) {
            long archivesId = Long.parseLong(node.getArchivesId());
            if (archivesId == -1) {
                continue;
            }
            GangGroupOrgPersonListBO gangGroupOrgPersonListBO = map.getOrDefault(archivesId, new GangGroupOrgPersonListBO());
            list.add(new GangGroupOrgPersonListVO(node, gangGroupOrgPersonListBO, creatorName));
        }
        //排序 先按更新数据库时间 再按图中的更新时间
        list = list.stream().sorted(Comparator.comparingLong(GangGroupOrgPersonListVO::getGraphUpdateTime).reversed()
                .thenComparing(GangGroupOrgPersonListVO::getArchivesUpdateTime).reversed()
        ).collect(toList());

        return list;
    }

    /**
     * 更新团伙(基本详情+组织关系)
     *
     * @param group 集团
     * @throws Exception 异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateGangGroupGraph(GangGroupSaveOrUpdateRequest group) throws Exception {
        // 更新团伙基本信息
        GangGroupInfoVO baseInfo = group.getBaseInfo();
        GangGroupInfo gangGroupInfo = new GangGroupInfo(baseInfo);
        Long id = group.getId();
        gangGroupInfo.setId(id);
        gangGroupInfo.setUpdateTime(new Date());
        boolean save = updateById(gangGroupInfo);
        AssertUtils.isTrue(save, CommonEnum.FAIL);
        // 保存团伙组织关系
        GangGroupOrgSaveOrUpdateRequest orgInfo = group.getOrgInfo();
        //生成团伙vid
        SessionWrapper sessionWrapper = nebulaSessionFactoryBean.getObject();
        String gangGroupVid = NebulaQueryUtils.buildVid(GraphTagEnum.GANG_GROUP.vidPrefix, id);
        //先删除
        deleteRoot(sessionWrapper, gangGroupVid);
        Set<String> vidList = new HashSet<>();
        //保存数据到图
        saveTreeToGraph(orgInfo, sessionWrapper, gangGroupVid, vidList);
        //保存团伙根vid关系
        saveGangGroupRootVidRelations(gangGroupVid, vidList);
    }

    private void saveTreeToGraph(GangGroupOrgSaveOrUpdateRequest orgInfo, SessionWrapper sessionWrapper, String gangGroupVid, Set<String> vidList) throws IOErrorException, UnsupportedEncodingException {
        //根节点
        boolean gangGroupSaveFlag = NebulaQueryUtils.insertVertex(sessionWrapper, gangGroupVid, GraphTagEnum.GANG_GROUP.tagName, null);
        AssertUtils.isTrue(gangGroupSaveFlag, CommonEnum.FAIL);

        List<GangGroupMembersBO> members = new ArrayList<>();
        vidList.add(gangGroupVid);
        //递归处理子节点
        handleGroupTree(orgInfo, gangGroupVid, members);
        //保存到图中
        if (CollectionUtils.isNotEmpty(members)) {
            for (GangGroupMembersBO member : members) {
                GangGroupMembersTag gangGroupMembersTag = new GangGroupMembersTag(member);
                String vid = member.getVid();
                String parentId = member.getParentId();
                boolean flag = NebulaQueryUtils.insertVertex(sessionWrapper, vid, GraphTagEnum.GANG_GROUP_MEMBERS.tagName, gangGroupMembersTag);
                AssertUtils.isTrue(flag, CommonEnum.FAIL);
                boolean edgeFlag = NebulaQueryUtils.insertEdge(sessionWrapper, parentId, vid, GraphEdgeEnum.ACCOMPLICE.edgeName, null);
                AssertUtils.isTrue(edgeFlag, CommonEnum.FAIL);
                vidList.add(vid);
                vidList.add(parentId);
                //插入团伙人员指向档案的关系
                String personArchivesId = member.getPersonArchivesId();
                if (StringUtils.isNotBlank(personArchivesId)) {
                    String personArchivesVid = NebulaQueryUtils.buildVid(GraphTagEnum.PERSON_ARCHIVES.vidPrefix, personArchivesId);
                    boolean personArchivesFlag = NebulaQueryUtils.insertEdge(sessionWrapper, vid, personArchivesVid, GraphEdgeEnum.BLANK.edgeName, null);
                    AssertUtils.isTrue(personArchivesFlag, CommonEnum.FAIL);
                }
            }
        }
    }

    private void deleteRoot(SessionWrapper session, String rootVid) throws IOErrorException, UnsupportedEncodingException {
        boolean delete = NebulaQueryUtils.deleteByGoRoot(session, rootVid, 1, 50, null);
        AssertUtils.isTrue(delete, CommonEnum.FAIL);
    }

    private GangGroupMembersGraphBO buildNode(NebulaGraph.NebulaNode from) {
        if (!from.getTagCode().equals(GraphTagEnum.GANG_GROUP_MEMBERS.tagName)) {
            return null;
        }
        String vid = (String) from.getVid();
        Map<String, Object> properties = from.getProperties();
        GangGroupMembersTag gangGroupMembersTag = JSONObject.parseObject(JSONObject.toJSONString(properties), GangGroupMembersTag.class);
        return new GangGroupMembersGraphBO(vid, gangGroupMembersTag);
    }

    private void handleGroupTree(GangGroupOrgSaveOrUpdateRequest root, String parentId, List<GangGroupMembersBO> members) {
        if (root == null) {
            return;
        }
        String shortId = IdWorker.getShortId();
        String vid = NebulaQueryUtils.buildVid(GraphTagEnum.GANG_GROUP_MEMBERS.vidPrefix, shortId);
        GangGroupMembersBO membersTag = new GangGroupMembersBO(vid, parentId, root);
        //根据传递的sha256值判断结点是否被更新了
        boolean updateArchives = handleUpdateArchives(root);
        if (updateArchives) {
            membersTag.setUpdateTime(System.currentTimeMillis() / 1000);
        }
        members.add(membersTag);
        if (CollectionUtils.isNotEmpty(root.getChildren())) {
            for (GangGroupOrgSaveOrUpdateRequest node : root.getChildren()) {
                handleGroupTree(node, vid, members);
            }
        }
    }

    /**
     * 根据传递的sha256值判断结点是否被更新了
     *
     * @param root 根
     */
    private boolean handleUpdateArchives(GangGroupOrgSaveOrUpdateRequest root) {
        String newSha256 = getSha256(root.getGroupIdentity(), root.getDegreeOfDanger());
        String oldSha256 = root.getSha256();
        boolean equals = Objects.equals(newSha256, oldSha256);
        return !equals;
    }

    private List<GangGroupOrgDetailVO> buildChildrenTree(Map<Long, GangGroupOrgPersonBO> idMapping, Map<String, List<GangGroupMembersGraphBO>> groupBy, String parentId, List<GangGroupOrgDetailVO> children) {
        List<GangGroupMembersGraphBO> bos = groupBy.get(parentId);
        if (CollectionUtils.isEmpty(bos)) {
            return Collections.emptyList();
        }
        for (GangGroupMembersGraphBO gangGroupRelationBO : bos) {
            GangGroupOrgPersonBO bo = idMapping.get(Long.parseLong(gangGroupRelationBO.getArchivesId()));
            GangGroupOrgDetailVO root = new GangGroupOrgDetailVO();
            String vid = gangGroupRelationBO.getVid();
            root.setId(vid);
            String groupIdentity = gangGroupRelationBO.getGroupIdentity();
            root.setGroupIdentity(groupIdentity);
            Integer degreeOfDanger = gangGroupRelationBO.getDegreeOfDanger() == null ? null : Integer.parseInt(gangGroupRelationBO.getDegreeOfDanger());
            root.setDegreeOfDanger(degreeOfDanger);
            root.setName(bo.buildNameList());
            root.setArchivesId(gangGroupRelationBO.getArchivesId());
            root.setPicture(bo.getPicture());
            root.setIdNumber(bo.getIdNumber());
            root.setPhoneNumber(bo.buildPhoneNumberList());
            root.setTags(bo.buildTagsList());
            root.setSocials(bo.buildSocialsList());
            //sha256 给到前端一个标识，如果后续前端修改了这个字可以判断这个节点是否被修改了
            root.setSha256(getSha256(groupIdentity, degreeOfDanger));
            root.setChildren(buildChildrenTree(idMapping, groupBy, vid, new ArrayList<>()));
            children.add(root);
        }
        return children;
    }

    private String getSha256(Object... obj) {
        return Hashing.sha256().hashString(StringUtils.join(obj, ""), StandardCharsets.UTF_8).toString();
    }

}
